package com.aug_leo.datastructure.blockingqueue;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 单锁实现
 *
 * @param <E> 元素类型
 */
@SuppressWarnings("All")
public class BlockingQueue1<E> implements BlockingQueue<E> {

    /**
     * 底层数组，用于存储队列元素
     */
    private final E[] array;

    /**
     * 头指针，用于指向队列头部元素的前一个位置
     */
    private int head;

    /**
     * 尾指针，用于指向队列尾部元素的位置
     */
    private int tail;

    /**
     * 队列中当前的元素个数
     */
    private int size;

    /**
     * 可重入锁，用于控制对队列操作的线程同步
     */
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 头部等待条件，用于管理队列头部元素的等待状态
     */
    private Condition headWaits = lock.newCondition();

    /**
     * 尾部等待条件，用于管理队列尾部元素的等待状态
     */
    private Condition tailWaits = lock.newCondition();


    public BlockingQueue1(int capacity) {
        array = (E[]) new Object[capacity];
    }

    /**
     * 将指定元素添加到此队列的尾部。
     * 如果在添加元素时检测到当前线程被中断，则抛出InterruptedException异常。
     *
     * @param e 要添加到队列的元素
     * @throws InterruptedException 如果当前线程在等待添加元素时被中断
     */
    @Override
    public void offer(E e) throws InterruptedException { // poll 等待队列非空
        lock.lockInterruptibly();
        try {
            while (isFull()) {
                tailWaits.await();
            }
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }
            size++;
            headWaits.signal();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 将指定元素添加到此队列的尾部，如果在给定的等待时间内队列未满则成功。
     * 如果在添加元素前检测到当前线程被中断，或者在等待超时后未能添加元素，则抛出InterruptedException异常。
     *
     * @param e       要添加到队列的元素
     * @param timeout 等待添加元素的时间，以毫秒为单位
     * @throws InterruptedException 如果当前线程在等待添加元素时被中断或超时
     */
    @Override
    public boolean offer(E e, long timeout) throws InterruptedException { // 毫秒 5s
        lock.lockInterruptibly();
        try {
            long t = TimeUnit.MILLISECONDS.toNanos(timeout);
            while (isFull()) {
                if (t <= 0) {
                    return false;
                }
                t = tailWaits.awaitNanos(t); // 最多等待多少纳秒    1s  4s  返回值代表剩余时间
            }
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }
            size++;
            headWaits.signal();
            return true;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 从此队列的头部移除并返回一个元素。
     * 如果在移除元素时检测到当前线程被中断，则抛出InterruptedException异常。
     *
     * @return 从此队列的头部移除的元素
     * @throws InterruptedException 如果当前线程在等待移除元素时被中断
     */
    @Override
    public E poll() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (isEmpty()) {
                headWaits.await(); // 等多久
            }
            E e = array[head];
            array[head] = null; // help GC
            if (++head == array.length) {
                head = 0;
            }
            size--;
            tailWaits.signal();
            return e;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 检查队列是否为空
     *
     * @return 空返回 true, 否则返回 false
     */
    private boolean isEmpty() {
        return size == 0;
    }

    /**
     * 检查队列是否已满
     *
     * @return 满返回 true, 否则返回 false
     */
    private boolean isFull() {
        return size == array.length;
    }

    @Override
    public String toString() {
        return Arrays.toString(array);
    }

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue1<String> queue = new BlockingQueue1<>(3);
        queue.offer("任务1");

        new Thread(()->{
            try {
                queue.offer("任务2");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "offer").start();

        new Thread(()->{
            try {
                System.out.println(queue.poll());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "poll").start();
    }

}
